(function (exports, _, utils){
	
	exports.Game = Game;

	function Game(player, width, height, mineFieldDesigner){
		var mineField; 
		var active = true;
		var startTime;

		function endIfWon(){
			if(_(mineField).chain().flatten().all(function (tile) { return tile.isMine() || tile.isCleared() }).value()) {
				var gameDuration = new Date().getTime() - startTime;
				player.win({'duration':gameDuration});
				active = false;
			}
		}

		function handle(statusReport) {
			if (statusReport === StatusReport.EXPLOSION) {
				player.lose(toJson(false));
				active = false;
	            	} else {
				player.update(toJson(true));
				endIfWon();
			}
		}
	
		function toJson(supressMines) {
			return {
                                 'grid' : _(mineField).map(function(row){
                                                return _(row).map(function(tile) {
                                                        return {
                                                                state : tile.publicView(supressMines),
                                                                numberOfAdjacentMines : tile.isCleared() ? tile.numberOfAdjacentMines() : '?'
                                                        };
                                                });
                                        })
			}
		}
	
		this.touch =  function (clickedCoord) {
			if (!active){
				return;
			}

			if (!startTime){
				startTime = new Date().getTime();
				mineField = createMineField(mineFieldDesigner, width, height, clickedCoord);
			}

			var clicked = mineField[clickedCoord.y][clickedCoord.x];
			handle(clicked.clear());
		};
	
		this.slam = function (clickedCoord) {
			if (!active) {
				return;
			}
			
			var clicked = mineField[clickedCoord.y][clickedCoord.x];
			handle(clicked.slam());
		};
	
		this.flag = function (coord) {
			mineField[coord.y][coord.x].flag();
		};
	}
	
	function createGrid(width, height) {
		return utils.fillArray(height,
				       function(){ return utils.fillArray(width,
									  function() { return new Tile(); })});
	}
	
	function createMineField(mineFieldDesigner, width, height, safeCoordinate){
		var mineField = createGrid(width, height);
		var mineFieldDesign = mineFieldDesigner.create(width, height, safeCoordinate);

		for (var y=0; y<height; y++) {
			for (var x=0; x<width; x++) {
				if (mineFieldDesign[y][x]){
					mineField[y][x].layMine();
				}
				mineField[y][x].addNeighbours(determineNeighbours(mineField, y,x));
			}	
		} 

		return mineField;
	}
	
	function determineNeighbours(mineField, y, x) {
		var neighbours = [];
		for (var yOffset=-1; yOffset<2; yOffset++){
			for (var xOffset=-1; xOffset<2; xOffset++){
				var potentialY = y + yOffset;
				var potentialX = x + xOffset;
				if ( potentialY >= 0 && potentialY < mineField.length && potentialX >= 0 && potentialX < mineField[0].length){
					neighbours.push(mineField[potentialY][potentialX]);
				}	
			}
		}
		return neighbours;
	}

	var StatusReport = {
		SAFE : {},
		EXPLOSION : {}
	}
	
	var State = 
		{
			COVERED_BUT_EMPTY : {publicView : function (supressMines) {return 'COVERED'}},
			CLEARED : {publicView : function (supressMines) {return 'CLEARED'}},
			MINE : {publicView : function (supressMines) {return supressMines ? State.COVERED_BUT_EMPTY.publicView(false) : 'MINE'}}
		}	

	function Tile(){
		var state = State.COVERED_BUT_EMPTY;
		var adjacentTiles = [];
		var flagged = false;

	        this.isMine = function () {
			return state === State.MINE;
		};
		this.isCleared = function () {
			return state === State.CLEARED;
		};
		this.numberOfAdjacentMines = function () {
			return _(adjacentTiles).filter(function (tile) {return tile.isMine()}).length;
		};
		this.publicView = function (supressMines) {
			return state.publicView(supressMines);
		};
		this.clear = function () {
			if (state == State.CLEARED || this.flagged) {
				return StatusReport.SAFE;
			}
			if (this.isMine()){
				return StatusReport.EXPLOSION;
			}

			state = State.CLEARED;
			if (_(adjacentTiles).all(function (tile) {return !tile.isMine() })) {
				_(adjacentTiles).each(function (tile) { tile.clear() });
			}
			return StatusReport.SAFE;
		};
		this.slam = function () {
			if (this.clear() == StatusReport.EXPLOSION) {
				return StatusReport.EXPLOSION;
			}
			if(_(adjacentTiles).chain().map(function (tile) { return tile.clear() }).
				any(function (statusReport) { return statusReport === StatusReport.EXPLOSION }).value()) {
				return StatusReport.EXPLOSION;
			}
			return StatusReport.SAFE;
		}
		this.flag = function () {
			this.flagged = ! this.flagged;
		};
		this.layMine = function () {
			state = State.MINE;
		};
		this.getState = function () {
			return state;
		}
		this.addNeighbours = function (neighbours) {
			adjacentTiles = neighbours;
		}
	}
	
	function createMineFieldDesigner(){
		return {
			create : function createTheGrid(width, height, clickedCoord) {
				function notTheClickedTile(x,y) {
					return (x !== clickedCoord.x) && (y !== clickedCoord.y);
				}
	
				var grid = utils.fillArray(height, function() { return utils.fillArray(width, false)} );	
				var mines = 10;
				while (mines > 0) {
					var x = utils.randomUpTo(height);
					var y = utils.randomUpTo(width);
					
					if (notTheClickedTile(x,y) && !grid[y][x]) {
						grid[y][x] = true;
						mines = mines - 1;
					}
				}
				return grid;
			}
		}
	}

})(typeof exports === 'undefined' ? this['model'] = {} : exports,
	typeof _ === 'undefined'? require('./external/underscore') : _,
	typeof utils === 'undefined' ? require('./utils') : utils);
